package org.dromara.common.excel.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.BusiException;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.excel.annotation.OnlyOne;
import org.dromara.common.excel.annotation.SubObject;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

@Data
@Slf4j
public class ConvertUtil<T> {

    /**
     * excel数据
     */
    private List<T> dataList;

    /**
     * 判断重复的属性方法
     */
    private String[] methodNames;

    /**
     * 子类
     */
    private String subObjectMethodName;

    /**
     * 主表class
     */
    private Class<?> mainClass;

    /**
     * 子表class
     */
    private String subClassName;

    /**
     * 构造函数
     */
    public ConvertUtil(Class<T> mainClass) {
        dataList = new ArrayList<>();
        this.mainClass = mainClass;
    }

    public ConvertUtil<T> invoke(List<?> dataList) {
        if (this.methodNames == null || this.methodNames.length == 0) {
            String[] methodNames = this.getOnlyOneAnnotation();
            if (methodNames == null || methodNames.length == 0) {
                throw new BusiException("未发现OnlyOne注解属性");
            }
            this.methodNames = methodNames;
        }
        if (this.subObjectMethodName == null) {
            String subObjectMethodName = this.getSubObjectAnnotation();
            if (subObjectMethodName != null) {
                this.subObjectMethodName = subObjectMethodName;
            }
        }
        for (Object object : dataList) {
            this.buildEntity(object);
        }
        return this;
    }

    /**
     * 添加数据
     *
     * @param vo
     */
    @SuppressWarnings("unchecked")
    private void buildEntity(Object vo) {
        T mainObject = this.checkDataIsExist(vo);
        Object subObject = null;
        if (this.subClassName != null) {

            try {//创建子数据对象
                subObject = Class.forName(this.subClassName).newInstance();
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            BeanUtil.copyProperties(vo, subObject);
        }
        if (mainObject == null) {//list中不存在主数据
            try {//创建主数据对象
                mainObject = (T) Class.forName(this.mainClass.getName()).newInstance();
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            BeanUtil.copyProperties(vo, mainObject);
            if (subObject != null) {
                List<Object> subClassList = new ArrayList<>();
                subClassList.add(subObject);
                ReflectUtil.invoke(mainObject, "set" + this.subObjectMethodName, subClassList);
            }
            dataList.add(mainObject);
        } else {//list中存在主数据，只添加子数据
            if (this.subObjectMethodName != null) {
                List<Object> subClassList = ReflectUtil.invoke(mainObject, "get" + this.subObjectMethodName);
                subClassList.add(subObject);
                ReflectUtil.invoke(mainObject, "set" + this.subObjectMethodName, subClassList);
            }
        }
    }

    /**
     * 判断数据是否存在
     */
    private T checkDataIsExist(Object object) {
        for (T obj : dataList) {
            boolean flag = true;
            for (String methodName : this.methodNames) {
                T methodValue1 = ReflectUtil.invoke(obj, "get" + methodName);
                Object methodValue2 = ReflectUtil.invoke(object, "get" + methodName);
                if (!methodValue1.equals(methodValue2)) {
                    flag = false;
                }
            }
            if (flag) {
                return obj;
            }
        }
        return null;
    }

    private String[] getOnlyOneAnnotation() {
        Field[] fields = this.getAllFields(this.mainClass);
        List<String> methodList = new ArrayList<>();
        for (Field field : fields) {
            // 只判断该字段拥有非空注解
            if (field.isAnnotationPresent(OnlyOne.class)) {
                //获取属性的名字
                String attributeName = field.getName();
                //将属性名字的首字母大写
                methodList.add(Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1));
            }
        }
        return methodList.toArray(new String[0]);
    }

    private String getSubObjectAnnotation() {
        Field[] fields = this.getAllFields(this.mainClass);
        for (Field field : fields) {
            // 只判断该字段拥有非空注解
            if (field.isAnnotationPresent(SubObject.class)) {
                Class<?> fieldType = field.getType();
                if (Collection.class.isAssignableFrom(fieldType)) {
                    java.lang.reflect.Type[] actualType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
                    if (actualType.length != 1 || actualType[0] == null || actualType[0].toString().length() < 5 || !Objects.equals(actualType[0].toString().substring(0, 5).toLowerCase(), "class")) {
                        // 非class类型的不处理
                        throw new ServiceException("SubObject非泛型");
                    }
                    this.subClassName = actualType[0].getTypeName();
                } else {
                    throw new ServiceException("SubObject注解属性必须是Collection实现类");
                }
                //获取属性的名字
                String attributeName = field.getName();
                //将属性名字的首字母大写
                return Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1);
            }
        }
        return null;
    }

    /**
     * <p>获取自己及继承自父类的所有字段，包括私有字段和受保护字段，可以使用反射 API 中的 getDeclaredFields() 方法来递归获取父类的字段。</p>
     *
     * @param clazz clazz
     * @return {@link Field[] }
     * @see Class<?>
     * @see Field[]
     */
    private Field[] getAllFields(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Class<?> parent = clazz.getSuperclass();
        if (parent != null) {
            Field[] parentFields = getAllFields(parent);
            Field[] allFields = new Field[fields.length + parentFields.length];
            System.arraycopy(fields, 0, allFields, 0, fields.length);
            System.arraycopy(parentFields, 0, allFields, fields.length, parentFields.length);
            fields = allFields;
        }
        return fields;
    }

}
